import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly.express as px
from datetime import datetime
import gradio as gr

# 用户认证信息
VALID_USERS = {
    "admin": "admin123",
    "user1": "password1",
    "user2": "password2"
}

# 图表选项
chart_options = [
    "1. 蜡烛图+成交量",
    "2. 移动平均线对比",
    "3. 收益率分布",
    "4. 月度箱线图",
    "5. 相关性热力图",
    "6. 3D散点图",
    "7. 波动率图",
    "8. 均线交叉图",
    "9. 成交量直方图",
    "10. 季节性子图"
]


def load_gold_data(file_path):
    """加载黄金数据并处理分号分隔符"""
    try:
        # 使用分号分隔符读取
        df = pd.read_csv(file_path, sep=';')

        # 处理列名合并情况
        if len(df.columns) == 1 and ';' in df.columns[0]:
            col_names = df.columns[0].split(';')
            df = pd.read_csv(file_path, sep=';', header=None)
            df.columns = col_names

        # 日期格式转换
        if 'Date' in df.columns:
            df['Date'] = pd.to_datetime(df['Date'])
            df = df.sort_values('Date')

        # 处理缺失值
        for col in df.columns:
            if df[col].isnull().sum() > 0:
                if df[col].dtype in ['float64', 'int64']:
                    df[col].fillna(df[col].median(), inplace=True)
                else:
                    df[col].fillna(df[col].mode()[0], inplace=True)

        # 处理异常值
        num_cols = df.select_dtypes(include=[np.number]).columns
        for col in num_cols:
            if col != 'Date':
                q1, q3 = df[col].quantile([0.25, 0.75])
                iqr = q3 - q1
                lower_bound = float(q1 - 1.5 * iqr)
                upper_bound = float(q3 + 1.5 * iqr)
                df[col] = df[col].astype(float)  # Convert to float first
                df.loc[df[col] < lower_bound, col] = lower_bound
                df.loc[df[col] > upper_bound, col] = upper_bound

        print(f"数据加载成功，列名: {df.columns.tolist()}")
        return df
    except Exception as e:
        print(f"数据加载失败: {str(e)}")
        return None


def create_candlestick_chart(df):
    """1. 蜡烛图+成交量组合"""
    fig = make_subplots(
        rows=2, cols=1, shared_xaxes=True,
        row_heights=[0.7, 0.3],
        specs=[[{"type": "candlestick"}], [{"type": "bar"}]]
    )
    fig.add_trace(go.Candlestick(
        x=df['Date'], open=df['Open'], high=df['High'],
        low=df['Low'], close=df['Close'], name='价格走势'
    ), row=1, col=1)
    for period in [5, 20, 60]:
        df[f'MA_{period}'] = df['Close'].rolling(period).mean()
        fig.add_trace(go.Scatter(
            x=df['Date'], y=df[f'MA_{period}'],
            name=f'{period}日MA', line=dict(width=2, dash='dash')
        ), row=1, col=1)
    if 'Volume' in df.columns:
        fig.add_trace(go.Bar(
            x=df['Date'], y=df['Volume'], name='成交量'
        ), row=2, col=1)
    fig.update_layout(title='黄金价格蜡烛图与成交量', height=800)
    return fig


def create_ma_comparison_chart(df):
    """2. 多周期移动平均线对比图"""
    fig = px.line(df, x='Date', y='Close',
                  title='黄金价格与多周期移动平均线',
                  labels={'Close': '收盘价（美元/盎司）'})
    for period in [5, 20, 60, 120]:
        df[f'MA_{period}'] = df['Close'].rolling(period).mean()
        fig.add_trace(go.Scatter(
            x=df['Date'], y=df[f'MA_{period}'],
            name=f'{period}日MA', line=dict(width=2)
        ))
    fig.update_layout(hovermode='x unified', height=600)
    return fig


def create_return_distribution_chart(df):
    """3. 收益率分布与正态拟合图"""
    df['Daily_Return'] = df['Close'].pct_change()
    df = df.dropna(subset=['Daily_Return'])
    fig = px.histogram(df, x='Daily_Return', nbins=50,
                       title='黄金价格收益率分布',
                       labels={'Daily_Return': '日收益率'})
    mean, std = df['Daily_Return'].mean(), df['Daily_Return'].std()
    x_range = np.linspace(df['Daily_Return'].min(), df['Daily_Return'].max(), 100)
    normal_dist = (1 / (np.sqrt(2 * np.pi) * std)) * np.exp(-0.5 * ((x_range - mean) / std) ** 2)
    fig.add_trace(go.Scatter(
        x=x_range, y=normal_dist * len(df) * (x_range[1] - x_range[0]),
        name='正态分布', line=dict(color='red', width=2)
    ))
    fig.update_layout(hovermode='x unified', height=600)
    return fig


def create_monthly_boxplot(df):
    """4. 月度价格箱线图"""
    df['YearMonth'] = df['Date'].dt.strftime('%Y-%m')
    fig = px.box(df, x='YearMonth', y='Close',
                 title='黄金收盘价月度箱线图',
                 labels={'Close': '收盘价（美元/盎司）'})
    fig.update_layout(xaxis=dict(tickangle=45), height=600)
    return fig


def create_correlation_heatmap(df):
    """5. 价格与成交量热力图"""
    lag_days = 5
    corr_data = pd.DataFrame({'Close': df['Close']})
    for i in range(1, lag_days + 1):
        corr_data[f'Close_lag_{i}'] = df['Close'].shift(i)
    if 'Volume' in df.columns:
        corr_data['Volume'] = df['Volume']
    corr_matrix = corr_data.corr()
    fig = px.imshow(corr_matrix, text_auto=True,
                    title='黄金价格与成交量相关性热力图')
    fig.update_layout(height=600)
    return fig


def create_3d_scatter_chart(df):
    """6. 3D价格-时间-成交量散点图"""
    if 'Volume' not in df.columns:
        return go.Figure()
    df['Date_num'] = (df['Date'] - df['Date'].min()).dt.days
    fig = px.scatter_3d(df, x='Date_num', y='Close', z='Volume',
                        color='Close',
                        title='黄金价格-时间-成交量3D散点图')
    fig.update_layout(height=700)
    return fig


def create_volatility_chart(df):
    """7. 价格波动率时间序列图"""
    df['Daily_Return'] = df['Close'].pct_change()
    df['Volatility'] = df['Daily_Return'].rolling(20).std() * 100
    fig = px.line(df, x='Date', y='Volatility',
                  title='黄金价格20日滚动波动率(%)',
                  labels={'Volatility': '波动率(%)'})
    fig.add_trace(go.Scatter(
        x=df['Date'], y=df['Volatility'].rolling(60).mean(),
        name='60日波动率均值', line=dict(color='red', width=2)
    ))
    fig.update_layout(height=600)
    return fig


def create_ma_cross_chart(df):
    """8. 移动平均线交叉信号图"""
    df['MA_5'] = df['Close'].rolling(5).mean()
    df['MA_20'] = df['Close'].rolling(20).mean()
    df['Signal'] = np.where(df['MA_5'] > df['MA_20'], 1, -1)
    df['Signal_change'] = df['Signal'].diff()

    fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
                        row_heights=[0.7, 0.3],
                        specs=[[{"type": "scatter"}], [{"type": "bar"}]])

    fig.add_trace(go.Scatter(
        x=df['Date'], y=df['Close'], name='收盘价', mode='lines'
    ))
    fig.add_trace(go.Scatter(
        x=df['Date'], y=df['MA_5'], name='5日MA', mode='lines'
    ))
    fig.add_trace(go.Scatter(
        x=df['Date'], y=df['MA_20'], name='20日MA', mode='lines'
    ))

    # 标记交叉点
    buy_signals = df[df['Signal_change'] == 2]
    sell_signals = df[df['Signal_change'] == -2]
    for _, signal in buy_signals.iterrows():
        fig.add_annotation(
            x=signal['Date'], y=signal['Low'],
            text="买入信号", showarrow=True,
            arrowhead=1, ax=0, ay=-30
        )
    for _, signal in sell_signals.iterrows():
        fig.add_annotation(
            x=signal['Date'], y=signal['High'],
            text="卖出信号", showarrow=True,
            arrowhead=1, ax=0, ay=30
        )

    if 'Volume' in df.columns:
        fig.add_trace(go.Bar(
            x=df['Date'], y=df['Volume'], name='成交量'
        ), row=2, col=1)

    fig.update_layout(title='移动平均线交叉交易信号图', height=800)
    return fig


def create_volume_histogram(df):
    """9. 成交量分布直方图"""
    if 'Volume' not in df.columns:
        return go.Figure()
    fig = px.histogram(df, x='Volume', nbins=30,
                       title='黄金成交量分布直方图',
                       labels={'Volume': '成交量'})
    fig.update_layout(height=600)
    return fig


def create_seasonality_subplots(df):
    """10. 价格季节性子图（年度周期分析）"""
    if len(df) < 365:
        return go.Figure()
    df['Year'] = df['Date'].dt.year
    df['Month'] = df['Date'].dt.month
    df['Day'] = df['Date'].dt.day

    # 按年份分组
    years = df['Year'].unique()
    fig = make_subplots(rows=len(years), cols=1, shared_xaxes=True,
                        subplot_titles=[f'{year}年黄金价格走势' for year in years])

    for i, year in enumerate(years):
        year_data = df[df['Year'] == year]
        fig.add_trace(go.Scatter(
            x=year_data['Month'] + (year_data['Day'] - 1) / 30,
            y=year_data['Close'],
            name=str(year)
        ), row=i + 1, col=1)

    fig.update_layout(title='黄金价格年度季节性对比', height=400 * len(years))
    return fig


def visualize_selected_chart(file_path, chart_type):
    """根据选择生成指定的可视化图表"""
    df = load_gold_data(file_path)
    if df is None or df.empty:
        return None

    chart_functions = {
        "1. 蜡烛图+成交量": create_candlestick_chart,
        "2. 移动平均线对比": create_ma_comparison_chart,
        "3. 收益率分布": create_return_distribution_chart,
        "4. 月度箱线图": create_monthly_boxplot,
        "5. 相关性热力图": create_correlation_heatmap,
        "6. 3D散点图": create_3d_scatter_chart,
        "7. 波动率图": create_volatility_chart,
        "8. 均线交叉图": create_ma_cross_chart,
        "9. 成交量直方图": create_volume_histogram,
        "10. 季节性子图": create_seasonality_subplots
    }

    if chart_type in chart_functions:
        return chart_functions[chart_type](df)
    return None


def authenticate(username, password):
    """验证用户登录"""
    if username in VALID_USERS and VALID_USERS[username] == password:
        return True
    return False


def create_app():
    """创建整合登录和主界面的应用"""
    with gr.Blocks(title="黄金数据分析可视化工具") as app:
        # 状态变量
        is_logged_in = gr.State(False)

        # 登录界面
        with gr.Column(visible=True) as login_col:
            gr.Markdown("# 黄金数据分析可视化工具")
            gr.Markdown("请先登录系统")

            username_input = gr.Textbox(label="用户名")
            password_input = gr.Textbox(label="密码", type="password")
            login_button = gr.Button("登录")
            status_output = gr.Textbox(label="登录状态", interactive=False)

        # 主界面 (初始时隐藏)
        with gr.Column(visible=False) as main_col:
            gr.Markdown("# 黄金数据分析可视化工具")
            gr.Markdown("选择数据文件和要查看的图表类型")

            with gr.Row():
                file_input = gr.File(label="上传数据文件", file_types=[".csv"])
                chart_dropdown = gr.Dropdown(
                    label="选择图表类型",
                    choices=chart_options,
                    value=chart_options[0]
                )

            plot_output = gr.Plot(label="可视化图表")
            submit_btn = gr.Button("生成图表")

            gr.Markdown("### 图表说明:")
            gr.Markdown("""
            - **蜡烛图+成交量**: 显示每日价格走势和成交量
            - **移动平均线对比**: 比较不同周期的移动平均线
            - **收益率分布**: 显示日收益率分布与正态分布对比
            - **月度箱线图**: 按月显示价格分布情况
            - **相关性热力图**: 显示价格与成交量的相关性
            - **3D散点图**: 3D展示价格、时间和成交量关系
            - **波动率图**: 显示价格波动率变化
            - **均线交叉图**: 显示交易信号点
            - **成交量直方图**: 显示成交量分布
            - **季节性子图**: 按年比较价格季节性模式
            """)

            logout_button = gr.Button("退出登录")

        # 登录逻辑
        def login(username, password):
            if authenticate(username, password):
                return {
                    login_col: gr.Column(visible=False),
                    main_col: gr.Column(visible=True),
                    status_output: "登录成功！",
                    is_logged_in: True
                }
            else:
                return {
                    status_output: "登录失败，用户名或密码错误！",
                    is_logged_in: False
                }

        # 退出逻辑
        def logout():
            return {
                login_col: gr.Column(visible=True),
                main_col: gr.Column(visible=False),
                is_logged_in: False,
                username_input: "",
                password_input: "",
                status_output: "已退出登录"
            }

        # 绑定事件
        login_button.click(
            fn=login,
            inputs=[username_input, password_input],
            outputs=[login_col, main_col, status_output, is_logged_in]
        )

        logout_button.click(
            fn=logout,
            outputs=[login_col, main_col, is_logged_in, username_input, password_input, status_output]
        )

        # 主界面功能
        submit_btn.click(
            fn=visualize_selected_chart,
            inputs=[file_input, chart_dropdown],
            outputs=plot_output
        )

    return app


if __name__ == "__main__":
    app = create_app()
    app.launch()